﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;

namespace AsianOptionsService
{
    public class KnockInOutOption : BasedOptions
    {
        bool flag = false;
        bool flag2 = false;

        public KnockInOutOption() { }

        public KnockInOutOption(int typeId, int dayNum, double interest, double initial, double exercise, double up, double down,
            double deltaT, double threshold, bool isCall, bool isFixed,bool isUp, int isAverage, int otherChoice, double var, int brownApproxiMethod)
        {
            this.typeId = typeId;

            this.dayNum = dayNum;

            this.interest = interest;

            this.initial = initial;

            this.exercise = exercise;

            this.up = up;

            this.down = down;

            this.deltaT = deltaT;

            this.threshold = threshold;

            this.isCall = isCall;

            this.isFixed = isFixed;

            this.isUp = isUp;

            /*
             * 1 is for ArithmeticAverageAsianOptions
             * 2 is for GeometricAverageAsianOptions
             * 0 is not average
             */ 
            this.isAverage = isAverage;

            /*
             * 1 is for Knock-in
             * 2 is for Knock-out
             */
            this.otherChoice = otherChoice;

            this.var = var;

            this.brownApproxiMethod = brownApproxiMethod;

            this.flag = false;
            this.flag2 = false;
        }

        public void isKnockInOut(double price)
        {
            if (isUp && otherChoice == 1) //up and in
            {
                if (price < threshold && !flag2)
                    flag2 = true;
                else if (flag2 && price > threshold)
                    flag = true;
            }
            else if (!isUp && otherChoice == 1) //down and in
            {
                if (price > threshold && !flag2)
                    flag2 = true;
                if (flag2 && price < threshold)
                    flag = true;
            }
            else if (!isUp && otherChoice == 2) //down and out
            {
                if (price > threshold && !flag2)
                    flag2 = true;
                if (flag2 && price < threshold)
                    flag = true;
            }
            else  //up and out
            {
                if (price < threshold && !flag2)
                    flag2 = true;
                if (price > threshold)
                    flag = true;
            }
        }

        public override double compute_price(int runs, int periods, int clientId)
        {
            double[] pricePath = new double[periods + 1];

            // Risk-neutral probabilities
            double piup = (interest - down) / (up - down);
            double pidown = 1 - piup;

            double temp = 0.0;

            Random rand = new Random();
            
            double callPayOff = 0.0;
            //double sumPayOff = 0.0;
            //double sumSquarePayOff = 0.0;
            double premium = 0.54;
            double[] payOffArray = new double[runs];
            int[] distribution = new int[100];

            for (int index = 0; index < runs; index++)
            {
                // Generate Path
                flag = false;
                flag2 = false;
   
                double sumPricePath = initial;
                double maxPrice = initial;
                double minPrice = initial;
                int lastIndex = periods;
                if (brownApproxiMethod == 0)
                {
                    
                    for (int i = 1; i <= periods; i++)
                    {
                        pricePath[0] = initial;
                        double rn = rand.NextDouble();

                        if (rn > pidown)
                        {
                            pricePath[i] = pricePath[i - 1] * up;
                        }
                        else
                        {
                            pricePath[i] = pricePath[i - 1] * down;
                        }

                        if (pricePath[i] > maxPrice)
                            maxPrice = pricePath[i];
                        else if (pricePath[i] < minPrice)
                            minPrice = pricePath[i];
                        sumPricePath += pricePath[i];

                        isKnockInOut(pricePath[i]);
                    }
                }
                else if (brownApproxiMethod == 1)
                {
                    
                    for (int i = 1; i <= periods; i++)
                    {
                        pricePath[0] = initial;
                        pricePath[i] = nextPrice(pricePath[i - 1], rand, sigma, interest, deltaT);
                        

                        if (pricePath[i] > maxPrice)
                            maxPrice = pricePath[i];
                        else if (pricePath[i] < minPrice)
                            minPrice = pricePath[i];
                        sumPricePath += pricePath[i];


                        isKnockInOut(pricePath[i]);
                    }
                }



                if (index % 10000 == 0)
                {
                    Log("pathId:" + (clientId * 5000000 / 32 + index / 10000) + "\r\n", clientId);
                    for (int i = 0; i <= periods; i++)
                        if (i != periods)
                            Log(pricePath[i] + "->", clientId);
                        else
                            Log(pricePath[i] + "\r\n\r\n", clientId);
                }
                if ((flag && otherChoice==1)||(!flag&&otherChoice==2))
                {
                    double priceAverage = 0;
                    if (isAverage == 1)
                        priceAverage = sumPricePath / (periods + 1); //ArithmeticAverageAsianOptions
                    else if(isAverage == 2)
                        priceAverage = Math.Exp(sumPricePath / (periods + 1)); //GeometricAverageAsianOptions


                    
                    if (isCall)
                    {
                        if(isAverage>=3 && isFixed)
                            callPayOff = Math.Max(maxPrice - exercise, 0);
                        else if(isAverage>=3 && !isFixed)
                            callPayOff = Math.Max(pricePath[periods] - minPrice, 0);
                        else if (isAverage > 0 && isFixed)
                            callPayOff = Math.Max(priceAverage - exercise, 0);
                        else if (isAverage > 0 && !isFixed)
                            callPayOff = Math.Max(pricePath[periods] - priceAverage, 0);
                        else if (isAverage <= 0 && isFixed)
                            callPayOff = Math.Max(pricePath[periods] - exercise, 0);
                        else
                            callPayOff = 0;

                    }
                    else
                    {
                        if (isAverage >= 3 && isFixed)
                            callPayOff = Math.Max(exercise - minPrice, 0);
                        else if (isAverage >= 3 && !isFixed)
                            callPayOff = Math.Max(maxPrice - pricePath[periods], 0);
                        else if (isAverage > 0 && isFixed)
                            callPayOff = Math.Max(exercise-priceAverage, 0);
                        else if (isAverage > 0 && !isFixed)
                            callPayOff = Math.Max(priceAverage-pricePath[periods], 0);
                        else if (isAverage <= 0 && isFixed)
                            callPayOff = Math.Max(exercise-pricePath[periods] , 0);
                        else
                            callPayOff = 0;
                    }
                }
                else
                {
                    callPayOff = 0;
                }
                //priceAverage = sumPricePath / (periods + 1);


                temp += callPayOff;
                payOffArray[index] = callPayOff;
                //sumPayOff += callPayOff / Math.Pow(interest, periods);
                //sumSquarePayOff += callPayOff * callPayOff / Math.Pow(interest, 2 * periods);
                int di = (int)(callPayOff / Math.Pow(interest, periods) * 20 / premium);
                if (di > 99)
                    di = 99;
                distribution[di]++;
            }

            double avgValue = (temp / Math.Pow(interest, periods)) / runs;
            double stdDev = 0;
            for (int i = 0; i < runs; i++)
                stdDev += Math.Pow((payOffArray[i] / Math.Pow(interest, periods) / avgValue - 1), 2);
            stdDev = Math.Sqrt(stdDev) / ((runs == 1) ? 1 : runs - 1);


            //double stdDev = Math.Sqrt(sumSquarePayOff - sumPayOff * sumPayOff / runs) / ((runs == 1) ? 1 : runs - 1);
            double stdErr = stdDev / Math.Sqrt(runs);
            Log(string.Format("stdDev :{0:0.0000} stdErr :{1:0.0000}\r\n", stdDev, stdErr), clientId);
            //double returnValue = (temp / Math.Pow(interest, periods)) / runs;
            for (int i = 0; i < 100; i++)
                Log(distribution[i] + " ", clientId);
            Log("\r\n", clientId);
            return avgValue;
        }
    }

}